﻿using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace SearchFrontEnd
{
	/// <summary>
	/// Class definition for a cache-able collection of items.  Specifically, 
	/// a maximum size for the cache may be defined and objects added will 
	/// be kept-alive whilst there is sufficient cache space.  Once an item
	/// has be pushed out of the cache, it is subject to the normal GC conditions.
	/// </summary>
	/// <remarks>Some limitations have been made on the types used in this
	/// generic.  Mainly, the <c>Key</c> must implement the <c>IComparable</c>
	/// interface and the <c>Value</c> must implement the <c>ICacheable</c>
	/// interface.</remarks>
	public class Cache
	{
		/// <summary>
		/// Alternative constructor allowing maximum cache size to be specified
		/// at construction time.
		/// </summary>
        /// <param name="maxEntries">Maximum size of the cache.</param>
        public Cache(int maxEntries)	
		{
			m_MaxEntries = maxEntries;

            // Create the stores.
            m_CacheStore = new Dictionary<string, string[]>();
            m_ArrivalOrder = new Queue<string>();
		}

	    /// <summary>
	    /// The <c>m_MaxEntries</c> variable stores the maximum size of
	    /// the item cache.  This is the sum of all of the items.  Once
	    /// this size is breached.  The cache starts dropping out items.
	    /// If they are reconstructible, then the items are destroyed but
	    /// left in the cache allowing them to come alive again.
	    /// </summary>
	    private int m_MaxEntries;

		public int MaxEntries
		{
			get { return m_MaxEntries; }
			set { m_MaxEntries = value; }
		}

		/// <summary>
		/// The private container representing the cache.
		/// </summary>
		private Dictionary<string, string[]> m_CacheStore;

		/// <summary>
		/// The private queue maintaining a basic order of items
		/// for purging when space necessitates.
		/// </summary>
        private Queue<string> m_ArrivalOrder;

	    /// <summary>
		/// This function is used when it has been determined that the cache is
		/// too full to fit the new item. It removes the oldest item in the cache.
		/// </summary>
      	private void PurgeSpace()
		{
			if (m_ArrivalOrder.Count > 0)
			{
                m_CacheStore.Remove(m_ArrivalOrder.Dequeue());
			}
		}

		/// <summary>
		/// The internal function to store the items within the cache.
		/// </summary>
		/// <param name="k">Identifier or key for the item.</param>
		/// <param name="v">The actual item to store/cache.</param>
		private void StoreItem(string k, string[] v)
		{
			m_ArrivalOrder.Enqueue(k);
			m_CacheStore.Add(k, v);
		}

		/// <summary>
		/// Add a new item into the cache.
		/// </summary>
		/// <param name="key">Identifier or key for item to add.</param>
		/// <param name="v">Actual item to store.</param>
        [MethodImpl(MethodImplOptions.Synchronized)]
		public void Add(string key, string[] v)
        {
	        // Check if we're using this yet
            if (m_CacheStore.ContainsKey(key))
            {
                // Simple replacement by removing and adding again, this
                // will ensure we do the size calculation in only one place.
                Remove(key);
            }

            // Need to get current total size and see if this will fit.
            if (m_CacheStore.Count + 1 > m_MaxEntries)
            {
                PurgeSpace();
            }

            // Store this value now..
            StoreItem(key, v);
		}

		/// <summary>
		/// Remove the specified item from the cache.
		/// </summary>
		/// <param name="key">Identifier for the item to remove.</param>
        [MethodImpl(MethodImplOptions.Synchronized)]
		public void Remove(string key)
		{
            if (m_CacheStore.ContainsKey(key))
            {
                RemoveKeyFromQueue(key);
                m_CacheStore.Remove(key);
            } 
		}

		/// <summary>
		/// Internal function to dequeue a specified value.
		/// </summary>
		/// <param name="key">Identifier of item to remove.</param>
		private void RemoveKeyFromQueue(string key)
		{
            if (m_ArrivalOrder.Contains(key))
			{
                if (m_ArrivalOrder.Peek().CompareTo(key) == 0)
                {
                    m_ArrivalOrder.Dequeue();
                }
				else
				{
					Queue<string> tempQueue = new Queue<string>();
					while (m_ArrivalOrder.Count > 0)
					{
						string tempValue = m_ArrivalOrder.Dequeue();

						if (tempValue.CompareTo(key) != 0)
						{
                            tempQueue.Enqueue(tempValue); 
						}
					}
					m_ArrivalOrder = tempQueue;
				}
			}
		}

		/// <summary>
		/// Touch or refresh a specified item.  This allows the specified
		/// item to be moved to the end of the dispose queue.  E.g. when it
		/// is known that this item would benefit from not being purged.
		/// </summary>
		/// <param name="key">Identifier of item to touch.</param>
		private void Touch(string key)
		{
			RemoveKeyFromQueue(key);
			m_ArrivalOrder.Enqueue(key);   // Put at end of queue.
		}

		/// <summary>
		/// Returns the item associated with the supplied identifier.
		/// </summary>
		/// <param name="key">Identifier for the value to be returned.</param>
		/// <returns>Item value corresponding to Key supplied.</returns>
		/// <remarks>Accessing a stored item in this way automatically
		/// forces the item to the end of the purge queue.</remarks>
        [MethodImpl(MethodImplOptions.Synchronized)]
		public string[] GetValue(string key)
		{
           if (m_CacheStore.ContainsKey(key))
            {
                Touch(key);
                return m_CacheStore[key];
            }
            return null;
		}

		/// <summary>
		/// Determines whether the cache contains the specific key.
		/// </summary>
		/// <param name="k">Key to locate in the cache.</param>
		/// <returns><c>true</c> if the cache contains the specified key; 
		/// otherwise <c>false</c>.</returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
		public bool ContainsKey(string k)
		{
		    return (m_CacheStore.ContainsKey(k));
		}
		
		/// <summary>
		/// Returns an enumerator that iterates through the collection of
		/// keys.
		/// </summary>
		/// <returns>The enumerator for keys.</returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
		public string[] GetKeys()
		{
            return m_ArrivalOrder.ToArray();  
		}

		/// <summary>
		/// Gets the number of items stored in the cache.
		/// </summary>
		/// <value>The number of items stored in the cache. </value>
		public int Count 
		{
            [MethodImpl(MethodImplOptions.Synchronized)]
			get
			{
			    return m_CacheStore.Count;
			} 
		}
	}
}
